package flowdroid.taint;

import cli.ConfigManager;
import db.DatabaseManager;
import flowdroid.MyFlowConfigManager;
import soot.Scene;
import soot.jimple.infoflow.android.SetupApplication;
import soot.jimple.infoflow.android.data.CategoryDefinition;
import soot.jimple.infoflow.android.data.parsers.CategorizedAndroidSourceSinkParser;
import soot.jimple.infoflow.sourcesSinks.definitions.ISourceSinkDefinition;
import soot.jimple.infoflow.sourcesSinks.definitions.SourceSinkType;
import tuple.TupleManager;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.Set;

import static util.Constant.getApkNameFromApkFilepath;

public class TaintPhase {
    public static SetupApplication taintFlow(String apkFileLocation, ConfigManager configManager,
                                             MyProvider myProvider, TupleManager manager)
            throws IOException, URISyntaxException {
        String androidJarPlatform = configManager.androidJarPlatform;
        String sourcePath=configManager.sourcePath;
        String sinkPath= configManager.sinkPath;

        long startTime = System.currentTimeMillis();

        // 1. configure the Soot/FlowDroid environment & load methods by constructing a call graph
        String classpath = Scene.v().getAndroidJarPath(androidJarPlatform, apkFileLocation);
        SetupApplication app = new SetupApplication(new File(androidJarPlatform), new File(apkFileLocation));
        MyFlowConfigManager.resetEnvironment();
        MyFlowConfigManager flowConfigManager = new MyFlowConfigManager(app);
        boolean openReconstruction = false; // no other methods in the path; faster but less Tuple
        flowConfigManager.configureAll(apkFileLocation, androidJarPlatform, classpath, openReconstruction, configManager);
        manager.prepareTaintFlow();


        // [KEY ACTION] 2. load sources & sinks
        Scene.v().loadBasicClasses();
        CategoryDefinition allCat = CategoryDefinition.ALL_CATEGORIES;
        Set<CategoryDefinition> tmpSet = new HashSet<>();
        tmpSet.add(allCat);

        CategorizedAndroidSourceSinkParser sourcesParser =
                new CategorizedAndroidSourceSinkParser(tmpSet,
//                        "/home/fyl/project/code/AndroPrivacyConsistency/data/config/Sources.txt"
                        sourcePath,
                        SourceSinkType.Source);
        Set<ISourceSinkDefinition> srcs = sourcesParser.parse();
        myProvider.addSourceFromDefinitionSet(srcs);

        CategorizedAndroidSourceSinkParser sinksParser =
                new CategorizedAndroidSourceSinkParser(tmpSet,
//                        "/home/fyl/project/code/AndroPrivacyConsistency/data/config/Sinks.txt",
                        sinkPath,
                        SourceSinkType.Sink);
        Set<ISourceSinkDefinition> sinks = sinksParser.parse();
        myProvider.addSinkFromDefinitionSet(sinks);
        myProvider.syncAfterChange();


        // [KEY ACTION] 3. run taint flow
        System.out.println("taint_flow");
        app.addResultsAvailableHandler(new MyInfoflowHandler(manager,configManager.recordFlowLog));
        app.runInfoflow(myProvider);

        long endTime = System.currentTimeMillis();
        double duration = (endTime - startTime) / 60000.0; // Convert to minutes
        try {
            DatabaseManager.insertDataFlowTime(getApkNameFromApkFilepath(apkFileLocation), "FLOW", duration);
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println("A DB error occurred. In file: TaintPhase.java, and caused by invoking insertDataFlowTime(...)");
        }
        return app;
    }
}
